/////////////////////////////////////////////////////////////////////////////
// Name:        ax_menu_speech_settings.cpp
// Purpose:     Speech settings menu class
// Author:      Julian Smart
// Modified by:
// Created:     2009-03-20
// RCS-ID:
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"
#include "wx/spinctrl.h"

#include "ax/ax_menu_alert.h"
#include "ax/ax_menu_speech_settings.h"
#include "ax/ax_speech.h"
#include "ax/ax_menu_toggle.h"
#include "ax/ax_menu_range.h"
#include "ax/ax_menu_choice.h"
#include "ax/ax_menu_folder.h"

/**
    @class AxSpeechProgramLocationValidator
 */

class AxSpeechProgramLocationValidator: public AxValidator
{
    DECLARE_DYNAMIC_CLASS( AxSpeechProgramLocationValidator )

public:
// Constructors

    AxSpeechProgramLocationValidator(const AxSpeechProgramLocationValidator& validator) { Init(); Copy(validator); }
    AxSpeechProgramLocationValidator(wxTextToSpeech* textToSpeech) { Init(); m_textToSpeech = textToSpeech; }
    AxSpeechProgramLocationValidator() { Init(); }

// Operations

    void Copy(const AxSpeechProgramLocationValidator& validator)
    {
        AxValidator::Copy(validator);

        m_textToSpeech = validator.m_textToSpeech;
    }

    virtual AxValidator* Clone() const { return new AxSpeechProgramLocationValidator(*this); }

    /// Override to pass a value from the UI to a data structure
    virtual bool TransferDataFromMenu();

    /// Override to pass a value from a data structure to the UI
    virtual bool TransferDataToMenu();

    /// Initialize variables
    void Init() { m_textToSpeech = NULL; }

protected:
    wxTextToSpeech* m_textToSpeech;
};

/**
    @class AxSpeechDataLocationValidator
 */

class AxSpeechDataLocationValidator: public AxValidator
{
    DECLARE_DYNAMIC_CLASS( AxSpeechDataLocationValidator )

public:
// Constructors

    AxSpeechDataLocationValidator(const AxSpeechDataLocationValidator& validator) { Init(); Copy(validator); }
    AxSpeechDataLocationValidator(wxTextToSpeech* textToSpeech) { Init(); m_textToSpeech = textToSpeech; }
    AxSpeechDataLocationValidator() { Init(); }

// Operations

    void Copy(const AxSpeechDataLocationValidator& validator)
    {
        AxValidator::Copy(validator);

        m_textToSpeech = validator.m_textToSpeech;
    }

    virtual AxValidator* Clone() const { return new AxSpeechDataLocationValidator(*this); }

    /// Override to pass a value from the UI to a data structure
    virtual bool TransferDataFromMenu();

    /// Override to pass a value from a data structure to the UI
    virtual bool TransferDataToMenu();

    /// Initialize variables
    void Init() { m_textToSpeech = NULL; }

protected:
    wxTextToSpeech* m_textToSpeech;
};


/*
 * AxSpeechSettingsMenuItem implementation.
 */

IMPLEMENT_CLASS( AxSpeechSettingsMenuItem, AxMenuItem )

BEGIN_EVENT_TABLE( AxSpeechSettingsMenuItem, AxMenuItem )
    EVT_CHOICE(AXID("SPEECHENGINE_MAIN"), AxSpeechSettingsMenuItem::OnSelectEngine)
    EVT_CHOICE(AXID("SPEECHENGINE_CONTENT"), AxSpeechSettingsMenuItem::OnSelectEngine)
    EVT_CHOICE(AXID("VOICE_MAIN"), AxSpeechSettingsMenuItem::OnSelectVoice)
    EVT_CHOICE(AXID("VOICE_CONTENT"), AxSpeechSettingsMenuItem::OnSelectVoice)
    EVT_SPINCTRL(AXID("VOLUME_MAIN"), AxSpeechSettingsMenuItem::OnSelectVolume)
    EVT_SPINCTRL(AXID("VOLUME_CONTENT"), AxSpeechSettingsMenuItem::OnSelectVolume)
    EVT_SPINCTRL(AXID("SPEED_MAIN"), AxSpeechSettingsMenuItem::OnSelectSpeed)
    EVT_SPINCTRL(AXID("SPEED_CONTENT"), AxSpeechSettingsMenuItem::OnSelectSpeed)
    EVT_CHECKBOX(AXID("ENABLE_MULTIPLEVOICES"), AxSpeechSettingsMenuItem::OnSelectMultipleVoices)
END_EVENT_TABLE()

AxSpeechSettingsMenuItem::AxSpeechSettingsMenuItem(AxMenuController* controller, AxSpeech* speech)
{
    m_speech = speech;
    m_controller = controller;

    SetLabel(_("Speech Settings"));
    SetDescription(_("Lets you change various speech options."));

    SetDynamic();

    AxSpeechSettingsActivator* activator = new AxSpeechSettingsActivator(speech);
    activator->SetMenuItem(this);
    SetActivator(activator);
}

void AxSpeechSettingsMenuItem::OnSelectEngine(wxCommandEvent& event)
{
    int channel;
    if (event.GetId() == AXID("SPEECHENGINE_MAIN"))
    {
        channel = AxSpeechMainChannel;
    }
    else
    {
        channel = AxSpeechContentChannel;
    }

    // Don't normalize the channel number if we really want to change the second voice settings.
    bool normalizeChannel = false;

    wxArrayString engineIds = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngineIds();
    wxArrayString engineNames = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngineDisplayNames();
    if (event.GetInt() >= 0 && event.GetInt() < (int) engineIds.GetCount())
    {
        wxString currentEngine = engineIds[event.GetInt()];
        wxString currentEngineName = engineNames[event.GetInt()];
        if (!currentEngine.IsEmpty())
        {
            m_speech->Stop();

            wxString oldEngine = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngine();
            int flags = 0;
            if (channel == AxSpeechContentChannel)
                flags = AxSpeechFlagsContentChannel;

            if (m_speech->GetTextToSpeech(channel, normalizeChannel).SetEngine(currentEngine))
            {
                m_controller->EmitSpeech(AxSpeechEventText, wxString::Format(_("Selected speech engine %s"), currentEngineName.c_str()),
                    AxNavigationHintNone, flags);
                m_speech->GetSpeechSettingsInfo(channel, normalizeChannel).m_engine = currentEngine;
            }
            else
                m_speech->GetTextToSpeech(channel, normalizeChannel).SetEngine(oldEngine);

            AxSpeechSettingsActivator* activator = wxDynamicCast(GetActivator(), AxSpeechSettingsActivator);
            if (activator)
            {
                AxChoiceMenuItem* voicesItem = activator->GetVoicesItem(channel);

                wxArrayString voices = m_speech->GetTextToSpeech(channel, normalizeChannel).GetAvailableVoices();
                wxString voice = m_speech->GetTextToSpeech(channel, normalizeChannel).GetVoice();
                int voiceIdx = voices.Index(voice);

                voicesItem->SetLabels(voices);
                voicesItem->SetDescriptions(voices);
                voicesItem->SetIds(voices);
                voicesItem->SetSelection(voiceIdx);
            }
            
            CheckCompatibleVoices();
        }
    }
}

void AxSpeechSettingsMenuItem::OnSelectVoice(wxCommandEvent& event)
{
    int channel;
    if (event.GetId() == AXID("VOICE_MAIN"))
        channel = AxSpeechMainChannel;
    else
        channel = AxSpeechContentChannel;

    // Don't normalize the channel number if we really want to change the second voice settings.
    bool normalizeChannel = false;

    wxString voice = event.GetString();
    m_speech->Stop();
    m_speech->GetTextToSpeech(channel, normalizeChannel).SetVoice(voice);

    CheckCompatibleVoices();
}

void AxSpeechSettingsMenuItem::OnSelectVolume(wxSpinEvent& event)
{
    int channel;
    if (event.GetId() == AXID("VOLUME_MAIN"))
        channel = AxSpeechMainChannel;
    else
        channel = AxSpeechContentChannel;

    // Don't normalize the channel number if we really want to change the second voice settings.
    bool normalizeChannel = false;

    int vol = event.GetPosition();
    m_speech->GetTextToSpeech(channel, normalizeChannel).SetVolume(vol, wxTTS_PERCENT);
}

void AxSpeechSettingsMenuItem::OnSelectSpeed(wxSpinEvent& event)
{
    int channel;
    if (event.GetId() == AXID("SPEED_MAIN"))
        channel = AxSpeechMainChannel;
    else
        channel = AxSpeechContentChannel;

    // Don't normalize the channel number if we really want to change the second voice settings.
    bool normalizeChannel = false;

    int speed = event.GetPosition();
    m_speech->GetTextToSpeech(channel, normalizeChannel).SetSpeed(speed, wxTTS_PERCENT);
}

void AxSpeechSettingsMenuItem::OnSelectMultipleVoices(wxCommandEvent& WXUNUSED(event))
{
    CheckCompatibleVoices();
}

void AxSpeechSettingsMenuItem::CheckCompatibleVoices()
{
    if (m_speech->GetPolicy().IsMultipleVoiceModeEnabled() &&
        !m_speech->AreVoicesCompatible())
    {
        m_speech->GetPolicy().EnableMultipleVoiceMode(false);

        AxModalAlertMenu alert(m_controller, this,
            _("Could not enable multiple voices"),
            _("The selected voices were not compatible for simultaneous speech, so multiple voices have been disabled."),
            _("Sorry, the current voices are incompatible"));
        alert.ShowModal();
    }
    m_speech->SetupMultipleVoices();
}

IMPLEMENT_DYNAMIC_CLASS( AxSpeechSettingsActivator,  AxActivator )

/// Called when the user activates a menu item by pressing return or double-clicking
AxMenu* AxSpeechSettingsActivator::Activate(AxMenuController* WXUNUSED(controller), AxMenuItem* WXUNUSED(it))
{
    AxMenu* menu = new AxMenu;
    menu->SetLabel(_("Speech Settings"));
    menu->SetDescription(_("Lets you change various speech options."));

    AxMenuItem* item = new AxToggleMenuItem(
        _("Speech is off"),
        _("Speech is on"),
        _("Switch speech on and off."),
        wxT("ENABLESPEECH"),
        m_speech->GetPolicy().m_enableSpeech);
    item->SetValidator(new AxToggleValidator(& m_speech->GetPolicy().m_enableSpeech));
    menu->AddItem(item);

    item = new AxToggleMenuItem(
        _("Multiple voices mode is off"),
        _("Multiple voices mode is on"),
        _("Switch multiple simultaneous voices mode on and off, for voicing content without it being interrupted by user interface voicing. Not all speech engines support several voices playing at the same time. However, you can often use different speech engines simultaneously."),
        wxT("ENABLE_MULTIPLEVOICES"),
        m_speech->GetPolicy().m_enableMultipleVoiceMode);
    item->SetValidator(new AxToggleValidator(& m_speech->GetPolicy().m_enableMultipleVoiceMode));
    menu->AddItem(item);

    item = new AxRangeMenuItem(_("Verbosity: %VALUE%"),
        _("Set the verbosity to a value between 1 (least verbose) and 5 (most verbose)."),
        wxT("VERBOSITY"),
        1, 5,
        m_speech->GetPolicy().m_verbosityLevel);
    item->SetValidator(new AxRangeValidator(& m_speech->GetPolicy().m_verbosityLevel));
    menu->AddItem(item);

    AxMenuItem* mainChannelMenuItem = new AxMenuItem(_("Main voice settings"),
        _("Change settings for the main voice, used for voicing the user interface."),
        wxT("MAINCHANNELITEM"));
    menu->AddItem(mainChannelMenuItem);

    AxMenu* mainChannelMenu = new AxMenu;
    mainChannelMenu->SetLabel(mainChannelMenuItem->GetLabel());
    mainChannelMenu->SetDescription(mainChannelMenuItem->GetDescription());
    mainChannelMenuItem->SetSubMenu(mainChannelMenu);

    AxMenuItem* contentChannelMenuItem = new AxMenuItem(_("Content voice settings"),
        _("Change settings for the content voice, used for voicing content such as large amounts of text."),
        wxT("CONTENTCHANNELITEM"));
    menu->AddItem(contentChannelMenuItem);

    AxMenu* contentChannelMenu = new AxMenu;
    contentChannelMenu->SetLabel(contentChannelMenuItem->GetLabel());
    contentChannelMenu->SetDescription(contentChannelMenuItem->GetDescription());
    contentChannelMenuItem->SetSubMenu(contentChannelMenu);

    // Don't normalize the channel number if we really want to change the second voice settings.
    bool normalizeChannel = false;

    int channel;
    for (channel = AxSpeechMainChannel; channel <= AxSpeechContentChannel; channel ++)
    {
        AxMenu* submenu = channel == AxSpeechMainChannel ? mainChannelMenu : contentChannelMenu;

        wxArrayString engineIds = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngineIds();
        wxArrayString engineNames = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngineDisplayNames();
        wxArrayString engineDescriptions = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngineDescriptions();
        wxString currentEngine = m_speech->GetTextToSpeech(channel, normalizeChannel).GetEngine();
        int sel = engineIds.Index(currentEngine);
        if (sel == -1)
            sel = 0;

        wxString channelSuffix;
        if (channel == AxSpeechMainChannel)
            channelSuffix = wxT("_MAIN");
        else
            channelSuffix = wxT("_CONTENT");
        
        item = new AxChoiceMenuItem(_("Speech engine: %VALUE%"),
            _("Choose a speech engine."),
            wxT("SPEECHENGINE") + channelSuffix,
            engineNames,
            engineIds,
            engineDescriptions,
            sel);
        submenu->AddItem(item);
        
        wxArrayString voices = m_speech->GetTextToSpeech(channel, normalizeChannel).GetAvailableVoices();
        wxString voice = m_speech->GetTextToSpeech(channel, normalizeChannel).GetVoice();
        int voiceIdx = voices.Index(voice);
        
        AxChoiceMenuItem* voicesItem = new AxChoiceMenuItem(_("Voice: %VALUE%"),
            _("Choose a voice."),
            wxT("VOICE") + channelSuffix,
            voices,
            voices,
            voices,
            voiceIdx);

        if (channel == AxSpeechMainChannel)
            m_mainVoicesItem = voicesItem;
        else
            m_contentVoicesItem = voicesItem;
        
        submenu->AddItem(voicesItem);
        
        item = new AxRangeMenuItem(_("Volume: %VALUE%%"),
            _("Set the volume to a value between 1 (quiet) and 100 (loud)."),
            wxT("VOLUME") + channelSuffix,
            1, 100,
            m_speech->GetSpeechSettingsInfo(channel, normalizeChannel).m_volume);
        item->SetValidator(new AxRangeValidator(& m_speech->GetSpeechSettingsInfo(channel, normalizeChannel).m_volume));
        submenu->AddItem(item);
        
        item = new AxRangeMenuItem(_("Speed: %VALUE%%"),
            _("Set the volume to a value between 1 (slow) and 100 (fast)."),
            wxT("SPEED") + channelSuffix,
            1, 100,
            m_speech->GetSpeechSettingsInfo(channel, normalizeChannel).m_speed);
        item->SetValidator(new AxRangeValidator(& m_speech->GetSpeechSettingsInfo(channel, normalizeChannel).m_speed));
        submenu->AddItem(item);

        AxFileMenuItem* commandLocation = new AxFileMenuItem(_("Program location: %FILE%"),
            _("Choose a command for the engine, where applicable."),
            wxT("COMMAND_LOCATION") + channelSuffix,
            m_speech->GetTextToSpeech(channel, normalizeChannel).GetProgramLocation());
        commandLocation->SetValidator(new AxSpeechProgramLocationValidator(& m_speech->GetTextToSpeech(channel, normalizeChannel)));
        submenu->AddItem(commandLocation);
        
        AxFolderMenuItem* dataLocation = new AxFolderMenuItem(_("Voice data location: %FOLDER%"),
            _("Choose a data location for the engine, where applicable."),
            wxT("DATA_LOCATION") + channelSuffix,
            m_speech->GetTextToSpeech(channel, normalizeChannel).GetDataLocation());
        dataLocation->SetValidator(new AxSpeechDataLocationValidator(& m_speech->GetTextToSpeech(channel, normalizeChannel)));
        submenu->AddItem(dataLocation);
        
        submenu->AddItem(new AxGoUpMenuItem);
    }

    menu->AddItem(new AxGoUpMenuItem);

    return menu;
}

IMPLEMENT_DYNAMIC_CLASS( AxSpeechProgramLocationValidator, AxValidator )

/// Override to pass a value from the UI to a data structure
bool AxSpeechProgramLocationValidator::TransferDataFromMenu()
{
    AxFileMenuItem* fileMenuItem = wxDynamicCast(GetMenuItem(), AxFileMenuItem);
    if (fileMenuItem)
    {
        if (m_textToSpeech->HasCapability(wxTTS_CAP_PROGRAM_LOCATION))
        {
            wxString oldDataLocation = m_textToSpeech->GetDataLocation();

            m_textToSpeech->SetProgramLocation(fileMenuItem->GetPath());

            wxString newDataLocation = m_textToSpeech->GetDataLocation();

            // Setting the program location may have caused the data location
            // to be guessed, and therefore the voices list to become available.
            // If so, set the new data location value and update the available voices. 
            
            if (oldDataLocation != newDataLocation && wxDirExists(newDataLocation))
            {
                
            }
        }
    }

    return true;
}

/// Override to pass a value from a data structure to the UI
bool AxSpeechProgramLocationValidator::TransferDataToMenu()
{
    AxFileMenuItem* fileMenuItem = wxDynamicCast(GetMenuItem(), AxFileMenuItem);
    if (fileMenuItem)
    {
        if (m_textToSpeech->HasCapability(wxTTS_CAP_PROGRAM_LOCATION))
        {
            fileMenuItem->SetPath(m_textToSpeech->GetProgramLocation());
            fileMenuItem->Enable(true);
        }
        else
        {
            fileMenuItem->SetPath(wxEmptyString);
            fileMenuItem->Enable(false);
        }
    }

    return true;
}

IMPLEMENT_DYNAMIC_CLASS( AxSpeechDataLocationValidator, AxValidator )

/// Override to pass a value from the UI to a data structure
bool AxSpeechDataLocationValidator::TransferDataFromMenu()
{
    AxFolderMenuItem* folderMenuItem = wxDynamicCast(GetMenuItem(), AxFolderMenuItem);
    if (folderMenuItem)
    {
        if (m_textToSpeech->HasCapability(wxTTS_CAP_DATA_LOCATION))
        {
            m_textToSpeech->SetDataLocation(folderMenuItem->GetPath());
        }
    }

    return true;
}

/// Override to pass a value from a data structure to the UI
bool AxSpeechDataLocationValidator::TransferDataToMenu()
{
    AxFolderMenuItem* folderMenuItem = wxDynamicCast(GetMenuItem(), AxFolderMenuItem);
    if (folderMenuItem)
    {
        if (m_textToSpeech->HasCapability(wxTTS_CAP_DATA_LOCATION))
        {
            folderMenuItem->SetPath(m_textToSpeech->GetDataLocation());
            folderMenuItem->Enable(true);
        }
        else
        {
            folderMenuItem->SetPath(wxEmptyString);
            folderMenuItem->Enable(false);
        }
    }

    return true;
}
